class Lexer:
    def __init__(self, string):
        self.tokens = []
        self.state = "root"
        self.stateStack = []
        
        self.lexFuncs =     self.collectFunctions("lex_")
        self.startFuncs =   self.collectFunctions("start_")
        self.endFuncs =     self.collectFunctions("end_")
        
        self.lex(string)
        
        self.pos = 0
        
    def done(self):
        return self.pos >= len(self.tokens)
    
    def peek(self):
        return self.tokens[self.pos]
        
    def next(self):
        assert not self.done(), "lexer overrun"
        ret = self.tokens[self.pos]
        self.pos += 1
        return ret
    
    def seek(self, d):
        self.pos += d
        assert self.pos >= 0 and self.pos < len(self.tokens)
        
    def reset(self):
        self.pos = 0
                
    def collectFunctions(self, prefix):
        funcs = {}
        for name in dir(self):
            if name.startswith(prefix) and callable(getattr(self,name)):
                funcs[name[len(prefix):]] = getattr(self,name)
        return funcs
                
    def lex(self, string):
        
        for char in string:
            assert self.state in self.lexFuncs, "no function for lex state %s" % self.state
            self.lexFuncs[self.state](char)
            
        return self.tokens
            
    def addToken(self, token, type = None):
        if not type: type = "symbol"
        self.tokens.append((token,type))
            
    def startState(self, state):
        if state in self.startFuncs:
            self.startFuncs[state]()
            
        self.stateStack.append(self.state)
        self.state = state
        
    def endState(self):
        assert self.stateStack, "lex stack underrun"
        
        if self.state in self.endFuncs:
            self.endFuncs[self.state]()
            
        self.state = self.stateStack.pop()
